// Copyright 2001-2005 freehep
package org.freehep.graphicsio.font;

import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.geom.Rectangle2D;
import java.io.*;

import org.freehep.graphics2d.font.CharTable;
import org.freehep.util.io.ASCIIHexOutputStream;
import org.freehep.util.io.CountedByteOutputStream;
import org.freehep.util.io.EEXECEncryption;

/**
 * Font embedder for type 1 fonts. The output can be directly fed into a ps file
 * or as a FontFile to a pdf file. <h3>Todo</h3>
 * <ul>
 * <li>use subroutines for accents
 * <li>add more hints
 * </ul>
 * 
 * @author Simon Fischer
 * @version $Id: FontEmbedderType1.java,v 1.4 2009/08/17 21:44:45 murkle Exp $
 */
public class FontEmbedderType1 extends FontEmbedder {

  /** Defines whether or not eexec encryption is used. */
  private static final boolean ENCRYPT = true;

  /**
   * Defines whether or not encrypted part should be hex encoded (otherwise it
   * is binary).
   */
  private static final boolean HEX_ENC = true;

  private final PrintStream fontFile;

  private PrintStream encrypted;

  private final CountedByteOutputStream byteCounter;

  private int asciiEnd, encEnd; // remember the lengths of the three
  // portions

  private final boolean addZeros;

  public FontEmbedderType1(FontRenderContext context, OutputStream out,
      boolean addZeros) {
    super(context);
    byteCounter = new CountedByteOutputStream(out);
    fontFile = new PrintStream(byteCounter);
    this.addZeros = addZeros;
    asciiEnd = encEnd = -1;
  }

  @Override
  protected void closeEmbedFont() throws IOException {
    encrypted.println("readonly put");
    encrypted.println("noaccess put");
    encrypted.println("dup /FontName get exch definefont pop");
    encrypted.print("mark");
    if (ENCRYPT)
      encrypted.print(" currentfile closefile ");
    encrypted.flush();
    encEnd = byteCounter.getCount();
    if (!ENCRYPT)
      asciiEnd = encEnd;

    if (addZeros) {
      fontFile.println();
      for (int i = 0; i < 16; i++)
        fontFile.println("00000000000000000000000000000000");
      fontFile.println("cleartomark");
    }
  }

  @Override
  protected void closeGlyphs() throws IOException {
    encrypted.println("end"); // end Private
    encrypted.println("end"); // end CharStrings
  }

  @Override
  protected void closeIncludeFont() {

    Rectangle2D boundingBox = getFontBBox();
    int llx = (int) Math.round(boundingBox.getX());
    int lly = (int) Math.round(boundingBox.getY());
    int urx = (int) Math.round(boundingBox.getX() + boundingBox.getWidth());
    int ury = (int) Math.round(boundingBox.getY() + boundingBox.getHeight());
    fontFile.println("/FontBBox {" + llx + " " + lly + " " + urx + " " + ury
        + "} readonly def");

    fontFile.println("currentdict end");

    // begin encrypted portion
    if (ENCRYPT) {
      fontFile.print("currentfile eexec ");
      asciiEnd = byteCounter.getCount();
    }
    fontFile.flush();
  }

  /** Returns the length of the ascii portion of the output. */
  public int getAsciiLength() {
    return asciiEnd;
  }

  /** Returns the length of the encrypted portion of the output. */
  public int getEncryptedLength() {
    return encEnd - asciiEnd;
  }

  @Override
  protected void openGlyphs() throws IOException {
    // begin encryption
    if (ENCRYPT) {
      if (HEX_ENC)
        encrypted = new PrintStream(new EEXECEncryption(
            new ASCIIHexOutputStream(fontFile), EEXECEncryption.EEXEC_R));
      else
        encrypted = new PrintStream(new EEXECEncryption(fontFile,
            EEXECEncryption.EEXEC_R));
    } else
      encrypted = fontFile;

    // begin the Private dictionary
    encrypted.println("dup /Private 8 dict dup begin");
    encrypted
        .println("/RD {string currentfile exch readstring pop} executeonly def");
    encrypted.println("/ND {noaccess def} executeonly def");
    encrypted.println("/NP {noaccess put} executeonly def");
    encrypted.println("/BlueValues [] def"); // ???
    encrypted.println("/MinFeature {16 16} def");
    encrypted.println("/password 5839 def");
    encrypted.print("2 index ");
    encrypted.println("/CharStrings " + (getNODefinedChars() + 1)
        + " dict dup begin");
  }

  @Override
  protected void openIncludeFont() throws IOException {

    // begin clear text ascii portion
    fontFile.println("%!FontType1-1.0: " + getFont().getName()); // unknown
    // version
    // number
    // fontFile.println("%%CreationDate: " +
    // DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).
    // format(new Date()));
    fontFile.println("% Generated by: " + getClass().getName());
    fontFile.println("11 dict begin");

    fontFile.println("/FontInfo 8 dict dup begin");
    fontFile.println("/FullName (" + getFont().getPSName() + ") readonly def");
    fontFile
        .println("/FamilyName (" + getFont().getFamily() + ") readonly def");
    fontFile.println("end readonly def");

    fontFile.println("/FontName /" + getFontName() + " def");
    fontFile.println("/PaintType 0 def");
    fontFile.println("/FontType 1 def");
    fontFile.println("/FontMatrix [" + 1 / FONT_SIZE + " 0.0 0.0 " + 1
        / FONT_SIZE + " 0.0 0.0] readonly def");
  }

  @Override
  protected void writeEncoding(CharTable t) throws IOException {
    fontFile.println("/Encoding 256 array");
    fontFile.println("0 1 255 {1 index exch /.notdef put} for"); // set
    // undefined
    // to
    // .notdef
    // ??
    for (int i = 0; i < 256; i++) {
      String charName = t.toName(i);
      if (charName != null)
        fontFile.println("dup " + i + " /" + charName + " put");
    }
    fontFile.println("readonly def");
  }

  @Override
  protected void writeGlyph(String characterName, Shape glyph,
      GlyphMetrics glyphMetrics) throws IOException {

    // FIXME: find out why Acrobat Reader displays some characters displaced
    // when
    // using the correct sidebearing. A value of 0 looks good
    double sidebearing = glyphMetrics != null ? glyphMetrics.getLSB() : 0;
    // double sidebearing = 0;

    // write the binary charstring to a buffer
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    CharstringEncoder charString = new CharstringEncoder(new EEXECEncryption(
        bytes, EEXECEncryption.CHARSTRING_R));

    charString.startChar(sidebearing, (glyphMetrics != null ? glyphMetrics
        .getAdvance() : getUndefinedWidth())); // bounds.getWidth());
    charString.drawPath(glyph);
    charString.endchar();

    // write the buffer to the encrypted fontFile
    byte[] binaryString = bytes.toByteArray();
    encrypted.print("/" + characterName + " " + binaryString.length + " RD ");
    for (byte element : binaryString)
      encrypted.write(element & 0x00ff);
    encrypted.println("ND");
    encrypted.flush();
  }

  @Override
  protected void writeWidths(double[] w) throws IOException {
  }

}
